/*
 * parser.c
 * Parser for uPL0c.
 *
 * Copyright 2024 dh33ex <dh33ex@riseup.net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA or visit <http://www.gnu.org/licenses/>.
 *
 *
 */

#include "parser.h"
#include "scaner.h"
#include "gios.h"
#include "generator.h"

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

/* element of a symbol table ( list actually :D ) */
typedef struct _symbol {
    char type;              /* C - constant; V - variable; P - procedure */
    char scope;             /* L - local; G - global */
    char id[ID_LEN];        /* identifier */
    int val;                /* if a variable/procedure - address; if a constant - value */

    struct _symbol *next;   /* next symbol */
} Symbol;

/*
 * element of a scopes list
 * the first scope is global, and the following are local
 *      module_scope -> proc1_scope -> proc2_scope -> NULL;
 */
typedef struct _scope {
    Symbol *symbols;        /* symbols of the current scope */
    int adr;
    struct _scope *next;    /* next scope */
} Scope;

static Scope *top_scope = NULL;
static Scope *current_scope = NULL;

/* internal functions */
static int expect(int tok, char *et);
static void factor(void);
static void term(void);
static void condition(int *main_jmp);
static void expression(void);
static void statment(void);
static void statment_sequence(void);
static void declarations(void);
static void program(void);
static void procedure(void);

static Symbol *find(char id[ID_LEN]);
static Symbol *find_current(char id[ID_LEN]);
static Symbol *new_symbol(char id[ID_LEN], int val, char type);

static void remove_symbol(Symbol *sym);
static void remove_scope(Scope *scp);

static void close_scope(void);
static void open_scope(void);

int compile(void) {
    /* read first token */
    next_token();

    /* allocate memory for global scope and setup it */
    top_scope = (Scope *) malloc(sizeof(Scope));
    current_scope = top_scope;
    top_scope->next = NULL;
    top_scope->symbols = NULL;
    top_scope->adr = 0;

    /* add configuration commands for SB and SP */
    put_f1(MOV, SB, 0, 0x400);
    put_f1(MOV, SP, 0, 0x470);

    /* start the parsing process */
    program();

    /* remove scopes and symbol tables */
    remove_scope(top_scope);

    return compiler.error;
}


static int expect(int tok, char *et) {
    if (compiler.token == tok) {
        return 1;
    }

    char error[16]; /* 6 for identifier + 1 for '\0' + 9 for " expected" */
    sprintf(error, "%s expected", et);

    print_error(error);
    return 0;
}


static void factor(void) {
    if (compiler.token == TOK_IDENT) {
        /* identifier */
        Symbol *var = find(compiler.id);
        next_token();
        if (var->type == 'V') {
            put_f2(LDW, RH, (var->scope == 'G' ? SB : SP), var->val);
            RH++;
        } else if (var->type == 'M') {
            put_f2(LDW, RH, 0, var->val);
            RH++;
        } else if (var->type == 'C') {
            put_f1(MOV, RH, 0, var->val);
            RH++;
        } else {
            print_error("using procedure ident as variable/const/memory");
        }
    } else if (compiler.token == TOK_NUMBER) {
        /* number */
        put_f1(MOV, RH, 0, compiler.val);
        next_token();
        RH++;
    } else if (compiler.token == TOK_LPAREN) {
        /* (expression) */
        next_token();

        expression();

        if (expect(TOK_RPAREN, ")")) { next_token(); }
    } else {
        print_error("factor: syntax error");
        next_token();
    }
}


static void term(void) {
    int op;
    factor();
    while (compiler.token == TOK_TIMES || compiler.token == TOK_DIV || compiler.token == TOK_AND || compiler.token == TOK_XOR || compiler.token == TOK_LSL || compiler.token == TOK_ROT || compiler.token == TOK_ASR) {
        op = compiler.token;
        next_token();
        factor();
        if (op == TOK_TIMES) {
            RH--;
            put_f0(MUL, RH-1, RH-1, RH);
        } else if (op == TOK_DIV) {
            RH--;
            put_f0(DIV, RH-1, RH-1, RH);
        } else if (op == TOK_AND) {
            RH--;
            put_f0(AND, RH-1, RH-1, RH);
        } else if (op == TOK_XOR) {
            RH--;
            put_f0(XOR, RH-1, RH-1, RH);
        } else if (op == TOK_LSL) {
            RH--;
            put_f0(LSL, RH-1, RH-1, RH);
        } else if (op == TOK_ASR) {
            RH--;
            put_f0(ASR, RH-1, RH-1, RH);
        } else if (op == TOK_ROT) {
            RH--;
            put_f0(ROT, RH-1, RH-1, RH);
        }
    }
}


static void expression(void) {
    int op;
    if (compiler.token == TOK_PLUS || compiler.token == TOK_MINUS || compiler.token == TOK_NOT) {
        op = compiler.token;
        next_token();
    }
    term();

    if (op == TOK_MINUS) {
        put_f1(MOV, RH, 0, 0);
        put_f0(SUB, RH-1, RH, RH-1);
    } else if (op == TOK_NOT) {
        put_f0(NOT, RH-1, 0, RH-1);
    }

    while (compiler.token == TOK_PLUS || compiler.token == TOK_MINUS || compiler.token == TOK_OR) {
        op = compiler.token;

        next_token();
        term();

        if (op == TOK_PLUS) {
            RH--;
            put_f0(ADD, RH-1, RH-1, RH);
        } else if (op == TOK_MINUS) {
            RH--;
            put_f0(SUB, RH-1, RH-1, RH);
        } else if (op == TOK_OR) {
            RH--;
            put_f0(OR, RH-1, RH-1, RH);
        }
    }
}


static void condition(int *main_jmp) {
    expression();
    int op;
    if (compiler.token == TOK_EQL || compiler.token == TOK_NEQ || compiler.token == TOK_LSS || compiler.token == TOK_LEQ || compiler.token == TOK_GTR || compiler.token == TOK_GEQ) {
        op = compiler.token;
        next_token();
        expression();

        if (op == TOK_EQL) {
            RH--;
            put_f0(SUB, RH-1, RH-1, RH);
            *main_jmp = put_f3(U_FLAG, JNE, 0);
        } else if (op == TOK_NEQ) {
            RH--;
            put_f0(SUB, RH-1, RH-1, RH);
            *main_jmp = put_f3(U_FLAG, JEQ, 0);
        } else if (op == TOK_LSS) {
            RH--;
            put_f0(SUB, RH-1, RH-1, RH);
            *main_jmp = put_f3(U_FLAG, JPL, 0);
        } else if (op == TOK_LEQ) {
            RH--;
            put_f0(SUB, RH-1, RH, RH-1);
            *main_jmp = put_f3(U_FLAG, JMI, 0);
        } else if (op == TOK_GTR) {
            RH--;
            put_f0(SUB, RH-1, RH, RH-1);
            *main_jmp = put_f3(U_FLAG, JPL, 0);
        } else if (op == TOK_GEQ) {
            RH--;
            put_f0(SUB, RH-1, RH, RH-1);
            *main_jmp = put_f3(U_FLAG, JMI, 0);
        }
    } else {
        print_error("condition: no comparising");
        next_token();
    }
}


static void statment(void) {
    if (compiler.token == TOK_IDENT) {
        /* variable assignment */
        Symbol *var = find(compiler.id);

        next_token();
        if (expect(TOK_BECOMES, ":=")) {
            next_token();
            expression();
            if (var->type == 'V') {
                RH--;
                put_f2(STW, RH, (var->scope == 'G' ? SB : SP), var->val);
            } else if (var->type == 'M') {
                RH--;
                put_f2(STW, RH, 0, var->val);
            } else {
                print_error("assign to procedure/const ident");
            }
        }
    } else if (compiler.token == TOK_CALL) {
        /* procedure call */
        next_token();

        if (expect(TOK_IDENT, "IDENT")) {
            Symbol *var = find(compiler.id);

            put_f3(V_FLAG | U_FLAG, JMP, (var->val - PC - 1)*4);

            next_token();
        }
    } else if (compiler.token == TOK_IF) {
        /* if branching */
        next_token();

        int main_jmp, sub_jmp;
        /* get address of main jmp */
        condition(&main_jmp);

        if (expect(TOK_THEN, "THEN")) { next_token(); }

        statment_sequence();
        if (compiler.token == TOK_ELSE) {
            sub_jmp = put_f3(U_FLAG, JMP, 0);
            code[main_jmp] += (PC-main_jmp-1)*4;
            main_jmp = sub_jmp;

            /* else branching */
            next_token();
            statment_sequence();
        }
        if (expect(TOK_END, "END")) {
            code[main_jmp] += (PC-main_jmp-1)*4;
            next_token();
        }
    } else if (compiler.token == TOK_WHILE) {
        /* while loop */
        next_token();

        int main_jmp, sub_jmp;
        sub_jmp = PC;

        /* get address of main jmp */
        condition(&main_jmp);

        if (expect(TOK_DO, "DO")) { next_token(); }
        statment_sequence();
        if (expect(TOK_END, "END")) {
            put_f3(U_FLAG, JMP, (sub_jmp - PC - 1)*4);
            code[main_jmp] += (PC - main_jmp - 1)*4;

            next_token();
        }
    } else {
        /* everything is fine, just an empty statement */
        print_note("statment: empty");
    }
}

static void statment_sequence(void) {
    statment();
    while (compiler.token == TOK_SEMICOLON) {
        next_token();
        /* one more statment */
        statment();
    }
}


static void declarations(void) {
    if (compiler.token == TOK_MEM) {
        /* memory declaration */
        do {
            next_token();
            if (expect(TOK_IDENT, "IDENT")) { next_token(); }
            if (expect(TOK_EQL, "=")) { next_token(); }
            if (expect(TOK_NUMBER, "NUMBER")) {
                new_symbol(compiler.id, compiler.val, 'M');
                next_token();
            }
        } while (compiler.token == TOK_COMMA);

        if (expect(TOK_SEMICOLON, ";")) { next_token(); }
    }
    if (compiler.token == TOK_CONST) {
        /* constant declaration */
        do {
            next_token();
            if (expect(TOK_IDENT, "IDENT")) { next_token(); }
            if (expect(TOK_EQL, "=")) { next_token(); }
            if (expect(TOK_NUMBER, "NUMBER")) {
                new_symbol(compiler.id, compiler.val, 'C');
                next_token();
            }
        } while (compiler.token == TOK_COMMA);

        if (expect(TOK_SEMICOLON, ";")) { next_token(); }
    }
    if (compiler.token == TOK_VAR) {
        /* variables declaration */
        do {
            next_token();
            if (expect(TOK_IDENT, "IDENT")) {
                new_symbol(compiler.id, current_scope->adr, 'V');
                current_scope->adr += 4;
                next_token();
            }
        } while (compiler.token == TOK_COMMA);

        if (expect(TOK_SEMICOLON, ";")) { next_token(); }
    }
}


static void program(void) {
    declarations();

    while (compiler.token == TOK_PROCEDURE) {
        next_token();
        procedure();
    }

    if (expect(TOK_BEGIN, "BEGIN")) { next_token(); }
    statment_sequence();
    if (expect(TOK_END, "END")) { next_token(); }
    if (expect(TOK_DOT, ".")) { next_token(); }
}


static void procedure(void) {
    /* procedure declaration */
    if (expect(TOK_IDENT, "IDENT")) {
        new_symbol(compiler.id, PROC, 'P');
        next_token();
    }
    open_scope();

    if (expect(TOK_SEMICOLON, ";")) { next_token(); }
    declarations();
    if (expect(TOK_BEGIN, "BEGIN")) { next_token(); }
    /* procedure body */
    unsigned int old_PC;
    old_PC = PC;

    PC = PROC;  /* move to procedure section */
    /* prolog */
    put_f1(SUB, SP, SP, 4 + current_scope->adr);
    put_f2(STW, LNK, SP, current_scope->adr);

    statment_sequence();

    /* epilog */
    put_f2(LDW, LNK, SP, current_scope->adr);
    put_f1(ADD, SP, SP, 4 + current_scope->adr);
    put_f3(0, JMP, LNK);

    PROC = PC;
    PC = old_PC;

    if (expect(TOK_END, "END")) { next_token(); }
    if (expect(TOK_SEMICOLON, ";")) { next_token(); }
    close_scope();
}





static void open_scope(void) {
    /* open local scope */
    Scope *result = current_scope;

    /* find end of the list */
    while (result->next != NULL) {
        result = result->next;
    }

    /* allocate memory for the new scope */
    result->next = (Scope *) malloc(sizeof(Scope));
    result = result->next;

    /* setup new scope */
    result->next = NULL;
    result->symbols = NULL;
    result->adr = 0;

    current_scope = result;
}


static void close_scope(void) {
    /* close local scope */
    current_scope = top_scope;
}


static void remove_symbol(Symbol *sym) {
    if (NULL == sym) {
        return;
    } else if (NULL == sym->next) {
        free(sym);
    } else {
        remove_symbol(sym->next);
    }
}


static void remove_scope(Scope *scp) {
    if (NULL == scp->next) {
        remove_symbol(scp->symbols);
        free(scp);
    } else {
        remove_scope(scp->next);
    }
}


static Symbol *new_symbol(char id[ID_LEN], int val, char type) {
    /* create a symbol in the current scope */
    Symbol *result = current_scope->symbols;
    Symbol *previous = NULL;

    /* find the end of a list OR the fact that the character already exists */
    while (result != NULL) {
        if (!strcmp(result->id, id)) {
            /* symbol already exist */
            print_error("double declaration");
            return result;
        }
        previous = result;
        result = result->next;
    }

    /* allocate memory for a new entry */
    result = (Symbol *) malloc(sizeof(Symbol));
    if (NULL == previous) {
        current_scope->symbols = result;
    } else {
        previous->next = result;
    }

    /* setup new symbol */
    result->next = NULL;
    result->val = val;
    result->type = type;
    result->scope = (current_scope == top_scope ? 'G' : 'L');
    strcpy(result->id, id);

    return result;
}


static Symbol *find(char id[ID_LEN]) {
    /* find symbol in local and global scopes */

    /* try to find symbol in current scope */
    Symbol *result = find_current(id);

    if (NULL == result) {
        /* try to find symbol in global scope */
        Scope *save = current_scope;
        current_scope = top_scope;
        result = find_current(id);
        current_scope = save;
    }

    if (NULL == result) {
        /* symbol does not exist */
        print_error("ident don't declared before");
    }

    return result;
}


static Symbol *find_current(char id[ID_LEN]) {
    /* find a symbol in the current scope */
    Symbol *result = current_scope->symbols;

    while (result != NULL) {
        if (!strcmp(result->id, id)) {
            /* symbol found, return it */
            return result;
        }
        result = result->next;
    }
    return NULL;
}


void print_symbols(void) {
    Scope *scp = top_scope;
    printf("Global scope\n");
    while (NULL != scp) {
        Symbol *sym = scp->symbols;
        while (NULL != sym) {
            printf("|--Symbol: %c %s %d\n", sym->type, sym->id, sym->val);
            sym = sym->next;
        }
        scp = scp->next;
        if (NULL != scp) {
            printf("Next scope\n");
        }
    }
}
